/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#ifndef STORAGE_SRC_STORAGE_FORMAT_ORC_INT128_H_
#define STORAGE_SRC_STORAGE_FORMAT_ORC_INT128_H_

#include <stdexcept>
#include <string>

#include "dbcommon/log/logger.h"

namespace orc {

// Represents a signed 128-bit integer in two's complement.
// Calculations wrap around and overflow is ignored.
//
// For a discussion of the algorithms, look at Knuth's volume 2,
// Semi-numerical Algorithms section 4.3.1.
class Int128 {
 public:
  Int128() {
    highbits = 0;
    lowbits = 0;
  }

  // Convert a signed 64 bit value into an Int128.
  Int128(int64_t right) {  // NOLINT
    if (right >= 0) {
      highbits = 0;
      lowbits = static_cast<uint64_t>(right);
    } else {
      highbits = -1;
      lowbits = static_cast<uint64_t>(right);
    }
  }

  // Create from the twos complement representation.
  Int128(int64_t high, uint64_t low) {
    highbits = high;
    lowbits = low;
  }

  // Parse the number from a base 10 string representation.
  explicit Int128(const std::string&);

  // Maximum positive value allowed by the type.
  static Int128 maximumValue();

  // Minimum negative value allowed by the type.
  static Int128 minimumValue();

  Int128& negate() {
    lowbits = ~lowbits + 1;
    highbits = ~highbits;
    if (lowbits == 0) {
      highbits += 1;
    }
    return *this;
  }

  Int128& abs() {
    if (highbits < 0) {
      negate();
    }
    return *this;
  }

  Int128& invert() {
    lowbits = ~lowbits;
    highbits = ~highbits;
    return *this;
  }

  // Add a number to this one. The result is truncated to 128 bits.
  // @param right the number to add
  // @return *this
  Int128& operator+=(const Int128& right) {
    uint64_t sum = lowbits + right.lowbits;
    highbits += right.highbits;
    if (sum < lowbits) {
      highbits += 1;
    }
    lowbits = sum;
    return *this;
  }

  // Subtract a number from this one. The result is truncated to 128 bits.
  // @param right the number to subtract
  // @return *this
  Int128& operator-=(const Int128& right) {
    uint64_t diff = lowbits - right.lowbits;
    highbits -= right.highbits;
    if (diff > lowbits) {
      highbits -= 1;
    }
    lowbits = diff;
    return *this;
  }

  // Multiply this number by a number. The result is truncated to 128 bits.
  // @param right the number to multiply by
  // @return *this
  Int128& operator*=(const Int128& right);

  // Divide this number by right and return the result. This operation is
  // not destructive.
  //
  // The answer rounds to zero. Signs work like:
  //    21 /  5 ->  4,  1
  //   -21 /  5 -> -4, -1
  //    21 / -5 -> -4,  1
  //   -21 / -5 ->  4, -1
  // @param right the number to divide by
  // @param remainder the remainder after the division
  Int128 divide(const Int128& right, Int128& remainder) const;  // NOLINT

  // Logical or between two Int128.
  // @param right the number to or in
  // @return *this
  Int128& operator|=(const Int128& right) {
    lowbits |= right.lowbits;
    highbits |= right.highbits;
    return *this;
  }

  // Logical and between two Int128.
  // @param right the number to and in
  // @return *this
  Int128& operator&=(const Int128& right) {
    lowbits &= right.lowbits;
    highbits &= right.highbits;
    return *this;
  }

  // Shift left by the given number of bits.
  // Values larger than 2**127 will shift into the sign bit.
  Int128& operator<<=(uint32_t bits) {
    if (bits != 0) {
      if (bits < 64) {
        highbits <<= bits;
        highbits |= (lowbits >> (64 - bits));
        lowbits <<= bits;
      } else if (bits < 128) {
        highbits = static_cast<int64_t>(lowbits) << (bits - 64);
        lowbits = 0;
      } else {
        highbits = 0;
        lowbits = 0;
      }
    }
    return *this;
  }

  // Shift right by the given number of bits. Negative values will
  // sign extend and fill with one bits.
  Int128& operator>>=(uint32_t bits) {
    if (bits != 0) {
      if (bits < 64) {
        lowbits >>= bits;
        lowbits |= static_cast<uint64_t>(highbits << (64 - bits));
        highbits =
            static_cast<int64_t>(static_cast<uint64_t>(highbits) >> bits);
      } else if (bits < 128) {
        lowbits = static_cast<uint64_t>(highbits >> (bits - 64));
        highbits = highbits >= 0 ? 0 : -1l;
      } else {
        highbits = highbits >= 0 ? 0 : -1l;
        lowbits = static_cast<uint64_t>(highbits);
      }
    }
    return *this;
  }

  bool operator==(const Int128& right) const {
    return highbits == right.highbits && lowbits == right.lowbits;
  }

  bool operator!=(const Int128& right) const {
    return highbits != right.highbits || lowbits != right.lowbits;
  }

  bool operator<(const Int128& right) const {
    if (highbits == right.highbits) {
      return lowbits < right.lowbits;
    } else {
      return highbits < right.highbits;
    }
  }

  bool operator<=(const Int128& right) const {
    if (highbits == right.highbits) {
      return lowbits <= right.lowbits;
    } else {
      return highbits <= right.highbits;
    }
  }

  bool operator>(const Int128& right) const {
    if (highbits == right.highbits) {
      return lowbits > right.lowbits;
    } else {
      return highbits > right.highbits;
    }
  }

  bool operator>=(const Int128& right) const {
    if (highbits == right.highbits) {
      return lowbits >= right.lowbits;
    } else {
      return highbits >= right.highbits;
    }
  }

  uint32_t hash() const {
    return static_cast<uint32_t>(highbits >> 32) ^
           static_cast<uint32_t>(highbits) ^
           static_cast<uint32_t>(lowbits >> 32) ^
           static_cast<uint32_t>(lowbits);
  }

  // Does this value fit into a long?
  bool fitsInLong() const {
    switch (highbits) {
      case 0:
        return !(lowbits & LONG_SIGN_BIT);
      case -1:
        return lowbits & LONG_SIGN_BIT;
      default:
        return false;
    }
  }

  // Convert the value to a long and
  int64_t toLong() const {
    if (fitsInLong()) {
      return static_cast<int64_t>(lowbits);
    }
    LOG_ERROR(ERRCODE_INTERNAL_ERROR, "Int128 too large to convert to long");
  }

  // Return the base 10 string representation of the integer.
  std::string toString() const;

  // Return the base 10 string representation with a decimal point,
  // the given number of places after the decimal.
  std::string toDecimalString(int32_t scale = 0) const;

  // Return the base 16 string representation of the two's complement with
  // a prefix of "0x".
  // Int128(-1).toHexString() = "0xffffffffffffffffffffffffffffffff".
  std::string toHexString() const;

  // Get the high bits of the twos complement representation of the number.
  int64_t getHighBits() { return highbits; }

  // Get the low bits of the twos complement representation of the number.
  uint64_t getLowBits() { return lowbits; }

  // Represent the absolute number as a list of uint32.
  // Visible for testing only.
  // @param array the array that is set to the value of the number
  // @param wasNegative set to true if the original number was negative
  // @return the number of elements that were set in the array (1 to 4)
  int64_t fillInArray(uint32_t* array, bool& wasNegative) const;  // NOLINT

 private:
  static const uint64_t LONG_SIGN_BIT = 0x8000000000000000u;
  int64_t highbits;
  uint64_t lowbits;
};

/**
 * Scales up an Int128 value
 * @param value the Int128 value to scale
 * @param power the scale offset. Result of a negative factor is undefined.
 * @param overflow returns whether the result overflows or not
 * @return the scaled value
 */
Int128 scaleUpInt128ByPowerOfTen(Int128 value, int32_t power, bool& overflow);
/**
 * Scales down an Int128 value
 * @param value the Int128 value to scale
 * @param power the scale offset. Result of a negative factor is undefined.
 * @return the scaled value
 */
Int128 scaleDownInt128ByPowerOfTen(Int128 value, int32_t power);
}  // end of namespace orc
#endif  // STORAGE_SRC_STORAGE_FORMAT_ORC_INT128_H_
